Skip to main content

4.2 - Dynamic Info- Minerals, Supply, and Time

If self.game_info is the unchanging blueprint of the battlefield, then the data available through self.state and its convenience properties represents your bot's real-time senses. This is the dynamic information—minerals, supply, unit positions, time—that changes on every single frame of the game.

Mastering your bot's senses is the key to reactive, intelligent behavior. This data is the input for every decision your bot will ever make.

The Core AI Loop: Sense, Think, Act

Your on_step method should be structured around this fundamental AI control loop.

.-------------------- SENSE ------------------------.
| - What are my resources? (self.minerals) |
| - How much supply do I have? (self.supply_left) |
| - Where are my units? (self.units) |
| - Where are the enemy units? (self.enemy_units) |
`---------------------------------------------------`
|
v
.-------------------- THINK --------------------.
| if self.minerals > 100: |
| if self.supply_left < 5: |
| if self.enemy_units.in_attack_range_of(x): |
`---------------------------------------------`
|
v
.--------------------- ACT ---------------------.
| await self.build(UnitTypeId.BARRACKS, ...) |
| barracks.train(UnitTypeId.MARINE) |
`-----------------------------------------------`

Your bot continuously cycles through this loop within on_step, sensing the world, applying its strategic logic, and issuing commands.


Key Dynamic Properties (Your Bot's Senses)

While the raw data lives in self.state, the BotAI class provides easy-to-use convenience properties. You should always prefer these helpers.

CategoryPropertyData TypePractical Use in a Project
Resourcesself.minerals
self.vespene
intCrucial for managing your bot's in-game economy, representing the current stockpile of minerals and vespene gas, respectively.
Supplyself.supply_used
self.supply_cap
self.supply_left
intTriggers the construction of new Supply Depots, Pylons, or Overlords to avoid being supply-blocked. The cap in self.supply_cap short for capacity.
Timeself.time
self.time_formatted
float
str
Used for timing build orders, scouting schedules, and logging events. self.time is more reliable than iteration.
Your Unitsself.units
self.structures
self.workers
self.townhalls
UnitsThe main tools for managing your forces. Used to find idle units, issue commands, and check army composition.
Enemy Unitsself.enemy_units
self.enemy_structures
UnitsThe primary input for all combat and reactive logic. Used to check for threats and identify targets.
Productionself.already_pending(unit_id)floatCrucial for preventing redundant actions, like building two Supply Depots when you only need one.
For RL DevsAll of the aboveN/AThese properties form the core of a structured observation space for a Reinforcement Learning agent. Normalizing these values (e.g., supply used / 200) is a common and effective practice.

A Developer's Decision-Making Checklist

When writing your on_step logic, follow this thought process:

  • 1. Am I alive? Check if you have townhalls. If not, you've likely lost.
  • 2. Can I spend my money? Check self.minerals and self.vespene. Idle resources are wasted resources.
  • 3. Am I supply-blocked? Check self.supply_left. If it's too low, building supply should be your #1 priority.
  • 4. Are my production buildings busy? Find idle townhalls, barracks, etc., and queue a unit.
  • 5. Am I under attack? Check if self.enemy_units are near your key structures.

Code Example: The Starter Macro Bot

This bot demonstrates a robust, basic macroeconomic cycle. It constantly builds workers and ensures it never gets supply-blocked—the two most important skills for any new bot.

# starter_macro_bot.py

from sc2.main import run_game
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId
from sc2.player import Bot, Computer
from sc2.data import Race, Difficulty


class MacroBot(BotAI):
"""
A bot that demonstrates a basic economic cycle using dynamic info.
1. Constantly builds workers.
2. Builds supply structures when needed.
"""

async def on_step(self, iteration: int):
# The two core tasks of macro: worker production and supply management.
await self.manage_worker_production()
await self.manage_supply()

async def manage_worker_production(self):
"""Builds workers from idle townhalls if we can afford them."""
# SENSE: Do we have any townhalls? Are they idle? Can we afford a worker?
if (
self.townhalls.exists
and self.can_afford(UnitTypeId.SCV)
and self.townhalls.idle.exists
):
# THINK: We have an idle townhall and money for an SCV.
# ACT: Train an SCV from a random idle townhall.
idle_townhall = self.townhalls.idle.random
idle_townhall.train(UnitTypeId.SCV)

async def manage_supply(self):
"""Builds a Supply Depot if we are running low on supply."""
# SENSE: Is our supply running low? Are we already building a depot?
# A good threshold is supply_left < (number of townhalls * 2) + 2
supply_threshold = self.townhalls.amount * 2 + 2
is_supply_low = self.supply_left < supply_threshold
is_depot_in_progress = self.already_pending(UnitTypeId.SUPPLYDEPOT) > 0

# THINK: If supply is low AND we aren't already building a depot, we must act.
if (
is_supply_low
and not is_depot_in_progress
and self.can_afford(UnitTypeId.SUPPLYDEPOT)
):
# ACT: Build a Supply Depot near our first townhall.
# self.find_placement is a powerful but potentially slow function.
build_location = await self.find_placement(
UnitTypeId.SUPPLYDEPOT,
near=self.townhalls.first.position,
max_distance=10,
)
if build_location:
# Use the built-in helper to select a worker for building.
worker = self.select_build_worker(build_location)
if worker:
worker.build(UnitTypeId.SUPPLYDEPOT, build_location)


if __name__ == "__main__":
run_game(
maps.get("AbyssalReefLE"),
[Bot(Race.Terran, MacroBot()), Computer(Race.Zerg, Difficulty.VeryEasy)],
realtime=True,
)